package com.runshu.rest;

import com.runshu.exception.ComErrorCode;
import com.runshu.exception.RestException;
import com.runshu.rest.converter.BeanConverter;
import com.runshu.rest.converter.ListConverter;
import com.runshu.utils.JsonUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.UriTemplate;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Created by DON on 2015/7/5.
 */
public class RestClientImpl implements RestClient {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private final static String DEFAULT_USER_AGENT = "RSY";

    PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
    private CloseableHttpClient httpClient;

    public RestClientImpl(final int executionCount, final int connectionMaxTotal, final int connectionDefaultMaxPerRoute) {
        HttpClientBuilder builder = HttpClientBuilder.create();

        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount1, HttpContext context) {
                if (executionCount1 >= executionCount) {
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    // Connection refused
                    return true;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return true;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // Retry if the request is considered idempotent
                    return true;
                }
                return false;
            }
        };
        //连接池最大生成连接数
        manager.setMaxTotal(connectionMaxTotal);
        // 默认设置route最大连接数 如访问一个域名最多
        manager.setDefaultMaxPerRoute(connectionDefaultMaxPerRoute);
        builder.setConnectionManager(manager);
        builder.setRetryHandler(httpRequestRetryHandler);
        builder.setUserAgent(DEFAULT_USER_AGENT);

        httpClient = builder.build();
    }

    /**
     * 证书
     * @param executionCount
     * @param connectionMaxTotal
     * @param connectionDefaultMaxPerRoute
     * @param sslsf
     *
     * @author Don
     * @date 2018-06-14 18:40:48
     */
    public RestClientImpl(final int executionCount, final int connectionMaxTotal, final int connectionDefaultMaxPerRoute, SSLConnectionSocketFactory sslsf) {
        HttpClientBuilder builder = HttpClientBuilder.create();

        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount1, HttpContext context) {
                if (executionCount1 >= executionCount) {
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    // Connection refused
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // Retry if the request is considered idempotent
                    return true;
                }
                return false;
            }
        };
        //连接池最大生成连接数200
        manager.setMaxTotal(connectionMaxTotal);
        // 默认设置route最大连接数为20
        manager.setDefaultMaxPerRoute(connectionDefaultMaxPerRoute);
//        builder.setConnectionManager(manager);

        builder.setRetryHandler(httpRequestRetryHandler);
        builder.setSSLSocketFactory(sslsf);
        builder.setUserAgent(DEFAULT_USER_AGENT);

        httpClient = builder.build();
    }

    /**
     * description: 超时限制
     *
     * @author Don
     * @date 2017-08-11 12:12:39
     */
    public RestClientImpl(final int executionCount, final int connectionMaxTotal, final int connectionDefaultMaxPerRoute,
                          final int connectTimeout, final int connectionRequestTimeout, final int socketTimeout) {
        HttpClientBuilder builder = HttpClientBuilder.create();

        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount1, HttpContext context) {
                if (executionCount1 >= executionCount) {
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    // Connection refused
                    return true;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return true;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // Retry if the request is considered idempotent
                    return true;
                }
                return false;
            }
        };

        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout)
                .setSocketTimeout(socketTimeout).build();

        //连接池最大生成连接数
        manager.setMaxTotal(connectionMaxTotal);
        // 默认设置route最大连接数 如访问一个域名最多
        manager.setDefaultMaxPerRoute(connectionDefaultMaxPerRoute);
        builder.setConnectionManager(manager);
        builder.setRetryHandler(httpRequestRetryHandler);
        builder.setDefaultRequestConfig(requestConfig);
        builder.setUserAgent(DEFAULT_USER_AGENT);

        httpClient = builder.build();
    }

    @Override
    public <T> T get(String url, Class<T> tClass, Object... urlVars) throws RestException {
        return get(url, tClass, null, urlVars);
    }

    @Override
    public <T> T get(String url, Class<T> tClass, Map<String, Object> params, Object... urlVars) throws RestException {
        if (params != null && params.size() > 0) {
            url = mergeParam(url, params);
        }
        return get(toURI(url, urlVars), tClass);
    }

    @Override
    public <T> T get(String url, Class<T> tClass, Map<String, Object> params, Map<String, Object> urlVars) throws RestException {
        if (params != null && params.size() > 0) {
            url = mergeParam(url, params);
        }
        return get(toURI(url, urlVars), tClass);
    }

    @Override
    public <T, V> V get(String url, Class<T> tClass, Map<String, Object> params, BeanConverter converter, Object... urlVars) throws RestException {
        if (params != null && params.size() > 0) {
            url = mergeParam(url, params);
        }
        return get(toURI(url, urlVars), tClass, converter);
    }

    @Override
    public <T, V> V get(String url, Class<T> tClass, Map<String, Object> params, BeanConverter converter, Map<String, Object> urlVars) throws RestException {
        if (params != null && params.size() > 0) {
            url = mergeParam(url, params);
        }
        return get(toURI(url, urlVars), tClass, converter);
    }

    @Override
    public <T> List<T> list(String url, Class<T> tClass, Map<String, Object> params, Object... urlVars) throws RestException {
        if (params != null && params.size() > 0) {
            url = mergeParam(url, params);
        }
        return get(toURI(url, urlVars), tClass, BeanConverter.list);
    }


    private <T, V> T get(URI uri, Class<V> tClass, BeanConverter converter) throws RestException {
        Result result = httpGet(uri);
        return resolve(tClass, result, converter);
    }

    private <T> T get(URI uri, Class<T> tClass) throws RestException {
        Result result = httpGet(uri);
        return resolve(tClass, result);
    }


    @Override
    public <T> T post(String url, Class<T> tClass, Map<String, Object> params, Object... urlVars) throws RestException {
        Result result = httpPost(toURI(url, urlVars), nameValuePair(params));
        return resolve(tClass, result);
    }

    @Override
    public <T> T post(String url, Class<T> tClass, String json) throws RestException {
        Result result = httpPost(toURI(url, ""), json);
        return resolve(tClass, result);
    }

    @Override
    public <T> T post(String url, Class<T> tClass, Map<String, Object> params, Map<String, String> cookies, List<Header> resHeaders) throws RestException {

        Result result = httpPost(toURI(url, ""), nameValuePair(params), cookies);
        for(Header header:result.getHeaders()) {
            resHeaders.add(header);
        }
        return resolve(tClass, result);
    }

    @Override
    public <T, V> V post(String url, Class<T> tClass, Map<String, Object> params, BeanConverter converter, Object... urlVars) throws RestException {
        Result result = httpPost(toURI(url, urlVars), nameValuePair(params));
        return resolve(tClass, result, converter);
    }

    @Override
    public <T> T post(String url, Class<T> tClass, Map<String, Object> params, Map<String, Object> urlVars) throws RestException {
        Result result = httpPost(toURI(url, urlVars), nameValuePair(params));
        return resolve(tClass, result);
    }

    @Override
    public <T> T postbody(String url, Class<T> tClass, String body) throws RestException {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
        Result result = execute(httpPost);

        if (result.getCode() != 200) {
            if (result.getLength() > 0) {
                throw RestException.Error(result.getCode(), new String(result.getContent(), StandardCharsets.UTF_8));
            } else {
                throw RestException.Error(ComErrorCode.ERROR_SERVER_CANNOT_PARSING);
            }
        } else {
            if (tClass.equals(Void.class) || tClass.equals(void.class)) {
                return null;
            } else {
                if (result.getLength() == 0) {
                    if (tClass.equals(String.class)) {
                        return (T) "";
                    } else {
                        throw RestException.Error(ComErrorCode.ERROR_SERVER_CANNOT_PARSING.Code(), "blank result");
                    }
                } else {
                    String response = new String(result.getContent(), StandardCharsets.UTF_8);
                    logger.info("url :" + url + " status code :" + result.getCode() + " content :" + response);
                    try {
                        T t = JsonUtils.toObject(response, tClass);
                        return t;
                    } catch (Exception e) {
                        RestResult errJson = RestResult.ERROR(result.getCode()).setErrorMessage("返回结果解析失败").put("response", response);
                        throw RestException.Error(ComErrorCode.ERROR_SERVER_CANNOT_PARSING.Code(), JsonUtils.toJson(errJson));
                    }
                }

            }

        }
    }


    @Override
    public void close() {
        if (this.httpClient != null) {
            try {
                this.httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            this.manager.close();
        }
    }

    private <T> T resolve(Class<T> tClass, Result result) throws RestException {
        return resolve(tClass, result, BeanConverter.simple);
    }

    private <T, V> T resolve(Class<V> tClass, Result result, BeanConverter beanConverter) throws RestException {
        if (result == null) {
            throw RestException.Error(ComErrorCode.ERROR_SERVER_NO_RESPONSE);
        }
        if (result.getCode() != 200) {
            logger.error(result.getCode() + ":" + new String(result.getContent(), StandardCharsets.UTF_8));
            if (result.getCode() == 500) {
                try {
                    Map<?, ?> map = BeanConverter.simple.converter(new String(result.getContent(), StandardCharsets.UTF_8), Map.class);
                    throw RestException.Error(Integer.valueOf(map.get("code").toString()), map.get("reason").toString());
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    throw RestException.Error(ComErrorCode.ERROR_SERVER_CANNOT_PARSING);
                }
            } else if (result.getCode() == 404) {
                throw RestException.Error(ComErrorCode.ERROR_SERVER_NO_URL);
            } else {
                throw RestException.Error(result.getCode(), ComErrorCode.ERROR_SERVER_INTERNAL.Msg());
            }
        }
        if (tClass.equals(Void.class) || tClass.equals(void.class)) {
            return null;
        }
        if (result.getLength() == 0) {
            if (beanConverter instanceof ListConverter) {
                return (T) new ArrayList();
            }
            return null;
        }

        return beanConverter.converter(new String(result.getContent(), StandardCharsets.UTF_8), tClass);
    }

    /**
     * 合并参数
     * @param url
     * @param params
     * @return
     */
    private String mergeParam(String url, Map<String, Object> params) {
        //String variable = URLEncodedUtils
        //        .format(nameValuePair(params), "UTF-8");
        StringBuilder variableBuilder = new StringBuilder();
        for (String key : params.keySet()) {
            if (params.get(key) != null) {
                String val = params.get(key).toString();
                try {
                    val = URLEncoder.encode(val, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                variableBuilder.append(key).append("=").append(val);
            }
            variableBuilder.append("&");
        }
        String variable = "";
        if (variableBuilder.length() > 0) {
            variable = variableBuilder.substring(0, variableBuilder.length() - 1);
        }

        if (!variable.trim().equals("")) {
            if (url.contains("?")) {
                url += "&" + variable;
            } else {
                url += "?" + variable;
            }
        }

        return url;
    }

    /**
     * map转参数
     *
     * @param params
     * @return
     */
    private List<NameValuePair> nameValuePair(Map<String, Object> params) {
        List<NameValuePair> parameters = new ArrayList<>();
        if (params == null) {
            return parameters;
        }
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            if (entry.getValue() instanceof Collection) {
                Collection coll = (Collection) entry.getValue();
                for (Object obj : coll) {
                    if (obj != null) {
                        parameters.add(new BasicNameValuePair(entry.getKey(), obj.toString()));
                    }
                }
            } else if (entry.getValue().getClass().isArray()) {
                Object[] objs = (Object[]) entry.getValue();
                for (Object obj : objs) {
                    if (obj != null) {
                        parameters.add(new BasicNameValuePair(entry.getKey(), obj.toString()));
                    }
                }
            } else {
                String value = entry.getValue().toString();
                if (value != null) {
                    parameters.add(new BasicNameValuePair(entry.getKey(), value));
                }
            }
        }

        return parameters;
    }

    private URI toURI(String url, Object... urlVars) {
        URI uri;
        if (urlVars != null && urlVars.length > 0) {
            uri = new UriTemplate(url).expand(urlVars);
        } else {
            //uri = new UriTemplate(url).expand();
            uri = URI.create(url);
        }
        return uri;
    }

    private URI toURI(String url, Map<String, Object> urlVars) {
        URI uri;
        if (urlVars != null && urlVars.size() > 0) {
            uri = new UriTemplate(url).expand(urlVars);
        } else {
            uri = new UriTemplate(url).expand();
        }
        return uri;
    }

    public Result httpGet(URI uri) {
        HttpGet httpGet = new HttpGet(uri);
        return execute(httpGet);
    }

    public Result httpPut(URI uri, List<NameValuePair> nameValuePair) {
        HttpPut httpPut = new HttpPut(uri);
        httpPut.setEntity(new UrlEncodedFormEntity(nameValuePair, Charset.forName("UTF-8")));
        return execute(httpPut);
    }


    private Result httpPost(URI toURI, List<NameValuePair> nameValuePair, Map<String, String> cookies) {
        HttpPost httpPost = new HttpPost(toURI);
        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePair, Charset.forName("UTF-8")));
        if (CollectionUtils.isEmpty(cookies)) {
            for (String key : cookies.keySet()) {
                httpPost.setHeader(key, cookies.get(key));
            }
        }
        return execute(httpPost);
    }

    /**
     * 进行post请求，content_type为application/x-www-form-urlencoded
     *
     * @param uri
     * @param nameValuePair
     * @return
     */
    public Result httpPost(URI uri, List<NameValuePair> nameValuePair) {
        HttpPost httpPost = new HttpPost(uri);
        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePair, Charset.forName("UTF-8")));
        return execute(httpPost);
    }

    /**
     * 进行post请求，content_type为application/json
     *
     * @param uri
     * @param json
     * @return
     */
    public Result httpPost(URI uri, String json) {
        HttpPost httpPost = new HttpPost(uri);
        // 创建请求内容
        StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
        httpPost.setEntity(entity);
        return execute(httpPost);
    }



    public Result httpDelete(URI uri) {
        HttpDelete httpDelete = new HttpDelete(uri);
        return execute(httpDelete);
    }

    public Result execute(HttpRequestBase request) {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        try {
            response = httpClient.execute(request);
            entity = response.getEntity();
            Header[] headers  = response.getHeaders("Set-Cookie");
            int code = response.getStatusLine().getStatusCode();
            byte[] bytes = EntityUtils.toByteArray(entity);
            long length = entity.getContentLength();
            if (code != 200) {
                logger.warn("http code：" + code);
                logger.warn("request abort \t URL: " + request.getURI().toString());
                request.abort();
            }
            return new Result(length, bytes, code, headers);
        } catch (Exception e) {
//            e.printStackTrace();
            request.abort();
            logger.error("httpclient调用异常\t URL: " + request.getURI().toString() + "\t" + e.getMessage());
        } finally {
            try {
                if (entity != null) {
                    EntityUtils.consume(entity);
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}
